msg_tool\scripts\yuris/
yser.rs

1//! Yu-Ris YSER files
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use serde::{Deserialize, Serialize};
10use std::io::{Read, Seek, Write};
11
12#[derive(Debug)]
13pub struct YSERBuilder {}
14
15impl YSERBuilder {
16    /// Creates a new instance of `YSERBuilder`
17    pub const fn new() -> Self {
18        YSERBuilder {}
19    }
20}
21
22impl ScriptBuilder for YSERBuilder {
23    fn default_encoding(&self) -> Encoding {
24        Encoding::Cp932
25    }
26
27    fn build_script(
28        &self,
29        buf: Vec<u8>,
30        _filename: &str,
31        encoding: Encoding,
32        _archive_encoding: Encoding,
33        config: &ExtraConfig,
34        _archive: Option<&Box<dyn Script>>,
35    ) -> Result<Box<dyn Script + Send + Sync>> {
36        Ok(Box::new(YSER::new(MemReader::new(buf), encoding, config)?))
37    }
38
39    fn extensions(&self) -> &'static [&'static str] {
40        &["ybn"]
41    }
42
43    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
44        if buf_len >= 4 && buf.starts_with(b"YSER") {
45            return Some(20);
46        }
47        None
48    }
49
50    fn script_type(&self) -> &'static ScriptType {
51        &ScriptType::YurisYSER
52    }
53
54    fn can_create_file(&self) -> bool {
55        true
56    }
57
58    fn create_file<'a>(
59        &'a self,
60        filename: &'a str,
61        writer: Box<dyn WriteSeek + 'a>,
62        encoding: Encoding,
63        file_encoding: Encoding,
64        config: &ExtraConfig,
65    ) -> Result<()> {
66        create_file(
67            filename,
68            writer,
69            encoding,
70            file_encoding,
71            config.custom_yaml,
72        )
73    }
74}
75
76#[derive(Debug, StructPack, StructUnpack, Deserialize, Serialize)]
77struct StringData {
78    unk: u32,
79    #[cstring]
80    s: String,
81}
82
83#[derive(Debug, StructPack, StructUnpack, Deserialize, Serialize)]
84struct YSERData {
85    engine: u32,
86    #[pvec(u64)]
87    strings: Vec<StringData>,
88}
89
90#[derive(Debug)]
91pub struct YSER {
92    data: YSERData,
93    custom_yaml: bool,
94}
95
96impl YSER {
97    pub fn new<T: Read + Seek>(
98        mut reader: T,
99        encoding: Encoding,
100        config: &ExtraConfig,
101    ) -> Result<Self> {
102        let mut sig = [0; 4];
103        reader.read_exact(&mut sig)?;
104        if &sig != b"YSER" {
105            anyhow::bail!("Unsupported YSER file.");
106        }
107        let data = YSERData::unpack(&mut reader, false, encoding, &None)?;
108        Ok(Self {
109            data,
110            custom_yaml: config.custom_yaml,
111        })
112    }
113}
114
115impl Script for YSER {
116    fn default_output_script_type(&self) -> OutputScriptType {
117        OutputScriptType::Custom
118    }
119
120    fn is_output_supported(&self, output: OutputScriptType) -> bool {
121        matches!(output, OutputScriptType::Custom)
122    }
123
124    fn default_format_type(&self) -> FormatOptions {
125        FormatOptions::None
126    }
127
128    fn custom_output_extension(&self) -> &'static str {
129        if self.custom_yaml { "yaml" } else { "json" }
130    }
131
132    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
133        let s = if self.custom_yaml {
134            serde_yaml_ng::to_string(&self.data)
135                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
136        } else {
137            serde_json::to_string_pretty(&self.data)
138                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
139        };
140        let mut writer = crate::utils::files::write_file(filename)?;
141        let s = encode_string(encoding, &s, false)?;
142        writer.write_all(&s)?;
143        writer.flush()?;
144        Ok(())
145    }
146
147    fn custom_import<'a>(
148        &'a self,
149        custom_filename: &'a str,
150        file: Box<dyn WriteSeek + 'a>,
151        encoding: Encoding,
152        output_encoding: Encoding,
153    ) -> Result<()> {
154        create_file(
155            custom_filename,
156            file,
157            encoding,
158            output_encoding,
159            self.custom_yaml,
160        )
161    }
162}
163
164fn create_file<'a>(
165    custom_filename: &'a str,
166    mut writer: Box<dyn WriteSeek + 'a>,
167    encoding: Encoding,
168    output_encoding: Encoding,
169    yaml: bool,
170) -> Result<()> {
171    let input = crate::utils::files::read_file(custom_filename)?;
172    let s = decode_to_string(output_encoding, &input, true)?;
173    let data: YSERData = if yaml {
174        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
175    } else {
176        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
177    };
178    writer.write_all(b"YSER")?;
179    data.pack(&mut writer, false, encoding, &None)?;
180    Ok(())
181}